/**
  ******************************************************************************
  * @file    sd_diskio.c
  * @author  MCD Application Team
  * @brief   SD Disk I/O driver.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2017 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */

  /* Includes ------------------------------------------------------------------*/
#include "ff_gen_drv.h"
#include "sd_diskio.h"

#include "qspi_w25q64.h"

#include "logs.h"

#define SD_DISKIO_LOG_ENABLE    (0)


/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* use the default SD timout as defined in the platform BSP driver*/
#if defined(SDMMC_DATATIMEOUT)
#define SD_TIMEOUT SDMMC_DATATIMEOUT
#elif defined(SD_DATATIMEOUT)
#define SD_TIMEOUT SD_DATATIMEOUT
#else
#define SD_TIMEOUT 30 * 1000
#endif

#define SD_DEFAULT_BLOCK_SIZE 512

/*
 * Depending on the usecase, the SD card initialization could be done at the
 * application level, if it is the case define the flag below to disable
 * the BSP_SD_Init() call in the SD_Initialize().
 */

 /* #define DISABLE_SD_INIT */

 /* Private variables ---------------------------------------------------------*/
 /* Disk status */
static volatile DSTATUS Stat = STA_NOINIT;

/* Private function prototypes -----------------------------------------------*/
static DSTATUS SD_CheckStatus(BYTE lun);
DSTATUS SD_initialize (BYTE);
DSTATUS SD_status (BYTE);
DRESULT SD_read (BYTE , BYTE* , DWORD , UINT);
#if _USE_WRITE == 1
DRESULT SD_write (BYTE , const BYTE* , DWORD , UINT);
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
DRESULT SD_ioctl (BYTE , BYTE , void*);
#endif  /* _USE_IOCTL == 1 */

const Diskio_drvTypeDef  SD_Driver = {
    SD_initialize,
    SD_status,
    SD_read,
    #if  _USE_WRITE == 1
    SD_write,
    #endif /* _USE_WRITE == 1 */

    #if  _USE_IOCTL == 1
    SD_ioctl,
    #endif /* _USE_IOCTL == 1 */
};

/**
  * @brief  Gets the current SD card data status.
  * @retval Data transfer state.
  *          This value can be one of the following values:
  *            @arg  SD_TRANSFER_OK: No data transfer is acting
  *            @arg  SD_TRANSFER_BUSY: Data transfer is acting
  */
uint8_t BSP_SD_GetCardState(void)
{
    return((HAL_SD_GetCardState(&hsd1) == HAL_SD_CARD_TRANSFER) ? SD_TRANSFER_OK : SD_TRANSFER_BUSY);
}

/* Private functions ---------------------------------------------------------*/
static DSTATUS SD_CheckStatus(BYTE lun)
{
    Stat = STA_NOINIT;

    if (0 == BSP_SD_GetCardState()) {
        Stat &= ~STA_NOINIT;
    }

    return Stat;
}

/**
  * @brief  Initializes a Drive
  * @param  lun : not used
  * @retval DSTATUS: Operation status
  */
DSTATUS SD_initialize(BYTE lun)
{
    Stat = STA_NOINIT;

    return SD_status(lun);
}

/**
  * @brief  Gets Disk Status
  * @param  lun : not used
  * @retval DSTATUS: Operation status
  */
DSTATUS SD_status(BYTE lun)
{
    switch (lun) {
        case SD_CARD_VOLUME: {
            return SD_CheckStatus(lun);
        } break;

        case SPI_FLASH_VOLUME: {
            extern int8_t OSPI_W25Qxx_AutoPollingMemReady(void);
            if (OSPI_W25Qxx_AutoPollingMemReady() == OSPI_W25Qxx_OK) {
                return 0;
            }
            logs_w("OSPI_W25Qxx_AutoPollingMemReady error.");
        } break;
    }
    return STA_NOINIT;
}

/**
  * @brief  Reads block(s) from a specified address in an SD card, in polling mode.
  * @param  pData: Pointer to the buffer that will contain the data to transmit
  * @param  ReadAddr: Address from where data is to be read
  * @param  NumOfBlocks: Number of SD blocks to read
  * @param  Timeout: Timeout for read operation
  * @retval SD status
  */
uint8_t BSP_SD_ReadBlocks(uint32_t *pData , uint32_t ReadAddr , uint32_t NumOfBlocks , uint32_t Timeout)
{
    if ( HAL_SD_ReadBlocks(&hsd1 , ( uint8_t * ) pData , ReadAddr , NumOfBlocks , Timeout) != HAL_OK ) {
        return MSD_ERROR;
    } else {
        return MSD_OK;
    }
}

/**
  * @brief  Reads Sector(s)
  * @param  lun : not used
  * @param  *buff: Data buffer to store read data
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to read (1..128)
  * @retval DRESULT: Operation result
  */
DRESULT SD_read(BYTE lun , BYTE *buff , DWORD sector , UINT count)
{
    DRESULT res = RES_ERROR;

    switch (lun) {
        case SD_CARD_VOLUME: {
            if ( BSP_SD_ReadBlocks(( uint32_t* ) buff ,
                ( uint32_t ) (sector) ,
                count , SD_TIMEOUT) == MSD_OK ) {
                /* wait until the read operation is finished */
                while ( BSP_SD_GetCardState() != MSD_OK ) {
                }
                //printf("sd read, sector = %ld, count = %d.\r\n", sector, count);
                res = RES_OK;
            } else {
                logs_e("SD_ReadBlocks error\n");
            }
        } break;

        case SPI_FLASH_VOLUME: {
            res = OSPI_W25Qxx_ReadBuffer((uint8_t *)buff, sector * SPI_FLASH_SECTOR_SIZE,
                                                     SPI_FLASH_SECTOR_SIZE * count);
        } break;
    }

#if SD_DISKIO_LOG_ENABLE
    logs_i("sd read, lun= %d, sector = %ld, count = %d, res:%d.", lun, sector, count, res);
#endif

    return res;
}

/**
  * @brief  Writes block(s) to a specified address in an SD card, in polling mode.
  * @param  pData: Pointer to the buffer that will contain the data to transmit
  * @param  WriteAddr: Address from where data is to be written
  * @param  NumOfBlocks: Number of SD blocks to write
  * @param  Timeout: Timeout for write operation
  * @retval SD status
  */
uint8_t BSP_SD_WriteBlocks(uint32_t *pData , uint32_t WriteAddr , uint32_t NumOfBlocks , uint32_t Timeout)
{
    if ( HAL_SD_WriteBlocks(&hsd1 , ( uint8_t * ) pData , WriteAddr , NumOfBlocks , Timeout) != HAL_OK ) {
        return MSD_ERROR;
    } else {
        return MSD_OK;
    }
}

/**
  * @brief  Writes Sector(s)
  * @param  lun : not used
  * @param  *buff: Data to be written
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to write (1..128)
  * @retval DRESULT: Operation result
  */
#if _USE_WRITE == 1
DRESULT SD_write(BYTE lun , const BYTE *buff , DWORD sector , UINT count)
{
    DRESULT res = RES_ERROR;
    uint32_t i = 0;

    switch (lun) {
        case SD_CARD_VOLUME: {
            if ( BSP_SD_WriteBlocks(( uint32_t* ) buff ,
                ( uint32_t ) (sector) ,
                count , SD_TIMEOUT) == MSD_OK ) {
                /* wait until the Write operation is finished */
                while ( BSP_SD_GetCardState() != MSD_OK ) {
                }
                res = RES_OK;
            }
        } break;

        case SPI_FLASH_VOLUME: {
            while ( i < count ) {
                OSPI_W25Qxx_SectorErase((uint32_t)((sector + i)*SPI_FLASH_SECTOR_SIZE));
                i++;
            }
            OSPI_W25Qxx_WriteBuffer((uint8_t *)buff, sector * SPI_FLASH_SECTOR_SIZE,
                                                     SPI_FLASH_SECTOR_SIZE * count);
            res = RES_OK;
        } break;
    }

#if SD_DISKIO_LOG_ENABLE
    logs_i("sd write, lun = %d, sector = %ld, count = %d, res: %d.", lun, sector, count, res);
#endif

    return res;
}
#endif /* _USE_WRITE == 1 */

/**
  * @brief  Get SD information about specific SD card.
  * @param  CardInfo: Pointer to HAL_SD_CardInfoTypedef structure
  */
void BSP_SD_GetCardInfo(HAL_SD_CardInfoTypeDef *CardInfo)
{
    /* Get SD card Information */
    HAL_SD_GetCardInfo(&hsd1 , CardInfo);
}

/**
  * @brief  I/O control operation
  * @param  lun : not used
  * @param  cmd: Control code
  * @param  *buff: Buffer to send/receive control data
  * @retval DRESULT: Operation result
  */
#if _USE_IOCTL == 1
DRESULT SD_ioctl(BYTE lun , BYTE cmd , void *buff)
{
    DRESULT res = RES_ERROR;
    BSP_SD_CardInfo CardInfo;

    switch (lun) {
        case SD_CARD_VOLUME: {
            if ( Stat & STA_NOINIT ) return RES_NOTRDY;
            switch ( cmd ) {
                /* Make sure that no pending write process */
            case CTRL_SYNC:
                res = RES_OK;
                break;

                /* Get number of sectors on the disk (DWORD) */
            case GET_SECTOR_COUNT:
                BSP_SD_GetCardInfo(&CardInfo);
                *( DWORD* ) buff = CardInfo.LogBlockNbr;
                res = RES_OK;
                break;

                /* Get R/W sector size (WORD) */
            case GET_SECTOR_SIZE:
                BSP_SD_GetCardInfo(&CardInfo);
                *( WORD* ) buff = CardInfo.LogBlockSize;
                res = RES_OK;
                break;

                /* Get erase block size in unit of sector (DWORD) */
            case GET_BLOCK_SIZE:
                BSP_SD_GetCardInfo(&CardInfo);
                *( DWORD* ) buff = CardInfo.LogBlockSize / SD_DEFAULT_BLOCK_SIZE;
                res = RES_OK;
                break;

            default:
                res = RES_PARERR;
                break;
            }
        } break;

        case SPI_FLASH_VOLUME: {
            switch ( cmd ) {
                case CTRL_SYNC:
                    res = RES_OK;
                    break;

                case GET_SECTOR_COUNT:
                    *( DWORD* ) buff = SPI_FLASH_SECTOR_COUNT;
                    res = RES_OK;
                    break;

                case GET_SECTOR_SIZE:
                    *( WORD* ) buff = SPI_FLASH_SECTOR_SIZE;
                    //logs_i("sd ioctl, SPI_FLASH_SECTOR_SIZE = %d, %d.", SPI_FLASH_SECTOR_SIZE);
                    res = RES_OK;
                    break;

                case GET_BLOCK_SIZE:
                    *( DWORD* ) buff = SPI_FLASH_BLOCK_SIZE;
                    res = RES_OK;
                    break;

                default:
                    res = RES_PARERR;
                    break;
            }
        } break;
    }

#if SD_DISKIO_LOG_ENABLE
    logs_i("sd ioctl, lun = %d, cmd = %d,  res = %d.", lun, cmd, res);
    if (NULL != buff) {
        logs_i("sd ioctl, buff = %d.", *(WORD *)buff);
    }
#endif
    
    return res;
}
#endif /* _USE_IOCTL == 1 */
